package hello

import (
	"appengine"
	"appengine/blobstore"
	"appengine/datastore"
	"appengine/user"
	"bytes"
	"crypto/sha1"
	"encoding/gob"
	"encoding/base64"
	"errors"
	"fmt"
	"html/template"
	"image"
	"image/jpeg"
	"io"
	"net/http"
	"resize"
	"runtime/debug"
	"strconv"
	"strings"
	"time"
)

var translitMap = map[string]string{
	"а": "a", "б": "b", "в": "v", "г": "g", "д": "d",
	"е": "e", "ё": "yo", "ж": "zh", "з": "z", "и": "i",
	"й": "j", "к": "k", "л": "l", "м": "m", "н": "n",
	"о": "o", "п": "p", "р": "r", "с": "s", "т": "t",
	"у": "u", "ф": "f", "х": "h", "ц": "c", "ч": "ch",
	"ш": "sh", "щ": "sch", "ь": "", "ы": "y", "ъ": "",
	"э": "ye", "ю": "yu", "я": "ya", " ": "-",
}

const loggedInSessAttr = "loggedIn"

func translit(s string) string {
	arr := strings.SplitN(s, "", len(s))
	resultArr := make([]string, len(arr), len(arr))
	for i := 0; i < len(arr); i++ {
		c := strings.ToLower(arr[i])
		if c == "#" || c == "?" {
			continue
		}
		translited := translitMap[c]
		if len(translited) != 0 {
			resultArr[i] = translited
		} else {
			resultArr[i] = c
		}
	}
	return strings.Join(resultArr, "")
}

type Params map[string]interface{}

func makeHandler(fn func(*appengine.Context, http.ResponseWriter, *http.Request, Params) string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// catch errors occured in action
		defer func() {
			if err := recover(); err != nil {
				if err == datastore.ErrNoSuchEntity {
					http.NotFound(w, r)
				}

				println("~~~~~~~~~Error occured~~~~~~~~~")
				debug.PrintStack()
				http.Error(w, err.(error).Error(), http.StatusInternalServerError)
				return
			}
		}()
		// call action
		c := appengine.NewContext(r)
		params := Params(map[string]interface{}{})
		tmpl := fn(&c, w, r, params)
		if len(tmpl) == 0 {
			return
		}

		// process template
		// add additional params
		usr := user.Current(c)
		redirectUrl := r.URL.Path + "?" + r.URL.RawQuery
		println("PATH:", redirectUrl)
		if usr == nil {
			loginUrl, err := user.LoginURL(c, redirectUrl)
			panicIfErr(err)
			params["LoginUrl"] = template.HTML(loginUrl)
		} else {
			logoutUrl, err := user.LogoutURL(c, redirectUrl)
			panicIfErr(err)
			params["LogoutUrl"] = template.HTML(logoutUrl)
		}
		params["c"] = c
		params["user"] = usr
		params["url"] = r.URL.Path
		params["categories"] = listAllCategories(&c)
		templates.ExecuteTemplate(w, tmpl, params)
	}
}

func makeAdminHandler(fn func(*appengine.Context, http.ResponseWriter, *http.Request)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// catch errors occured in action
		defer func() {
			if err := recover(); err != nil {
				println("~~~~~~~~~Error occured~~~~~~~~~")
				debug.PrintStack()
				http.Error(w, err.(error).Error(), http.StatusInternalServerError)
				return
			}
		}()
		if isLoggedIn(r) {
			// call action
			c := appengine.NewContext(r)
			fn(&c, w, r)
		} else {
			http.Redirect(w, r, "/loginForm", http.StatusFound)
		}
	}
}

func isLoggedIn(r *http.Request) bool {
	cookie, _ := r.Cookie(loggedInSessAttr)
	if cookie == nil {
		return false;
	}

	c := appengine.NewContext(r)
	savedCookieVal := getAuthCookie(&c)
	return cookie.Value == savedCookieVal
}

var errMethodNotPost = errors.New("Method is not POST")

func panicIfErr(err error) {
	if err != nil {
		panic(err)
	}
}

func notFoundOrPanic(err error, w http.ResponseWriter, r *http.Request) {
	if err != nil {
		if err == datastore.ErrNoSuchEntity {
			http.NotFound(w, r)
			return
		}
		panic(err)
	}
}

func saveImage(r *http.Request, name string) (appengine.BlobKey, error) {
	if r.Method != "POST" {
		return appengine.BlobKey(""), errMethodNotPost
	}

	f, _, err := r.FormFile(name)
	if f == nil {
		return appengine.BlobKey(""), nil
	}
	panicIfErr(err)
	defer f.Close()

	// Grab the image data
	var buf bytes.Buffer
	io.Copy(&buf, f)
	i, _, err := image.Decode(&buf)
	panicIfErr(err)

	// Resize if too large, for more efficient moustachioing.
	// We aim for less than 1200 pixels in any dimension; if the
	// picture is larger than that, we squeeze it down to 600.
	const max = 1200
	if b := i.Bounds(); b.Dx() > max || b.Dy() > max {
		// If it's gigantic, it's more efficient to downsample first
		// and then resize; resizing will smooth out the roughness.
		if b.Dx() > 2*max || b.Dy() > 2*max {
			w, h := max, max
			if b.Dx() > b.Dy() {
				h = b.Dy() * h / b.Dx()
			} else {
				w = b.Dx() * w / b.Dy()
			}
			i = resize.Resample(i, i.Bounds(), w, h)
			b = i.Bounds()
		}
		w, h := max/2, max/2
		if b.Dx() > b.Dy() {
			h = b.Dy() * h / b.Dx()
		} else {
			w = b.Dx() * w / b.Dy()
		}
		i = resize.Resize(i, i.Bounds(), w, h)
	}

	// Encode as a new JPEG image.
	buf.Reset()
	err = jpeg.Encode(&buf, i, nil)
	panicIfErr(err)

	// write image to blobstore	
	blobWriter, err := blobstore.Create(appengine.NewContext(r), "image/jpg")
	panicIfErr(err)
	_, err = blobWriter.Write(buf.Bytes())
	panicIfErr(err)
	err = blobWriter.Close()
	panicIfErr(err)

	key, err := blobWriter.Key()
	panicIfErr(err)
	return key, err
}

func write(w http.ResponseWriter, data string) {
	fmt.Fprint(w, data)
}

func parseInt(s string) (i int64) {
	i, err := strconv.ParseInt(s, 10, 64)
	panicIfErr(err)
	return
}

func parseFloat(s string) (f float64) {
	f, err := strconv.ParseFloat(s, 64)
	panicIfErr(err)
	return
}

func parseBool(s string) (b bool) {
	b, err := strconv.ParseBool(s)
	panicIfErr(err)
	return
}

func formBool(s string) bool {
	if s == "on" {
		return true
	}
	return false
}

func raw(s string) template.HTML {
	return template.HTML(s)
}

func formatTime(t time.Time) string {
	return t.Format(time.Stamp)
}

const salt = "salt ;-)"

func hashString(s string) string {
	// encode string
	buf := bytes.NewBuffer(nil)
	enc := gob.NewEncoder(buf)
	enc.Encode(s + salt + formatTime(time.Now()))
	// make hash
	hash := sha1.New()
	hash.Write(buf.Bytes())	
	return base64.StdEncoding.EncodeToString(hash.Sum(nil))
}

type AuthCookie struct {
	Key *datastore.Key
	Value string
}

func createAuthCookieKey(c *appengine.Context) *datastore.Key {
	return datastore.NewKey(*c, "AuthCookie", "", 1, nil)
}

func saveAuthCookie(c *appengine.Context, value string) {
	cookie := &AuthCookie{Value: value}
	key, err := datastore.Put(*c, createAuthCookieKey(c), cookie)
	panicIfErr(err)
	cookie.Key = key
	datastore.Put(*c, key, cookie)
}

func getAuthCookie(c *appengine.Context) string {
	cookie := new(AuthCookie)
	err := datastore.Get(*c, createAuthCookieKey(c), cookie)
	// here can be ErrNoSuchEntity
	// so probably no login was executed before
	if err == datastore.ErrNoSuchEntity {
		return ""
	}
	panicIfErr(err)
	return cookie.Value
}

func bytesToString(b []byte) string {
	return bytes.NewBuffer(b).String()
}

func recipeMetaDescription(s string) string {
	if len(s) > 120 {
		s = s[:160]
		dotIndex := strings.LastIndex(s, ".")
		if dotIndex != -1 {
			s = s[:dotIndex+1]
		}
	}
	return s
}

func substr(s string, count int) string {
	if len(s) <= count {
		return s
	}

	buff := bytes.NewBufferString("")
	for index, rune := range s {
		if index >= count {
			break;
		}
		buff.Write([]byte(string(rune)))
	}
	return buff.String()
}
